"""

Adder - Arithmetic Parser

"""

from pyparsing import *
ParserElement.enablePackrat()
import sys
from StringCodes import StringCodes

OPERATOR = 0
FIRSTOPERAND = 1
NEXTOPERAND = 2
SKIPOPERAND = 3

VARIABLE = 0
FUNCARGS = 1
ARRAYINDEX = 2
VARIABLE_ARRAYONLY = 3

NUMBER = 4
STRING = 5
FUNCTION = 6

class ArithmeticParser():
	def __init__(self, variable, parent):
		self.variable = variable
		self.parent = parent
		
		self.escaper = StringCodes()
	
	def parseExpression(self, expression, returnResultRequired = False):
		expr = Forward()
		
		double = Word(nums + ".").setParseAction(lambda t:float(t[0]))
		integer = Word(nums).setParseAction(lambda t:int(t[0]))
		variable = Word(alphanums + "_")
		string = dblQuotedString
		funccall = Group(variable + "(" + Group(Optional(delimitedList(expr))) + ")")
		array_func = funccall + "[" + Group(delimitedList(expr, "][")) + "]"
		array_var = Group(variable + "[" + Group(delimitedList(expr, "][")) + "]")
		
		operand = double | string | array_func | funccall | array_var | variable

		equalityop = oneOf('== <= >= < > !=')
		expop = Literal('^')
		signop = oneOf('+ -')
		multop = oneOf('* /')
		plusop = oneOf('+ -')
		classop = Literal('.')
		
		expr << operatorPrecedence( operand,
		[(classop, 2, opAssoc.LEFT),
			("^", 2, opAssoc.RIGHT),
			(signop, 1, opAssoc.RIGHT),
			(multop, 2, opAssoc.LEFT),
			(plusop, 2, opAssoc.LEFT),
			(equalityop, 2, opAssoc.LEFT),]
		)
		
		# Use pyParsing to interpret the expression
		try:
			result = expr.parseString(expression)
		except ParseException as e:
			error = str(e)
			self.fatalError(expression, "Unable to parse line.  Exact error: \n  " + error)
		
		# Now convert to bytecode.
		# The result will be on the register as mT0.
		if (not returnResultRequired):
			# It's a single call
			mTCount, bytecode = self.parseExpressionToBytecode(result, 0, False)
		else:
			# There's an expression containing multiple terms, therefore, functions
			# must return a value
			mTCount, bytecode = self.parseExpressionToBytecode(result, 0, True)

		return bytecode
	
	def isSingularCall(self, result):
		# Check if the variable expression does not end in a function.
		a = result[-1]
		if (isinstance(a, str)):
			if (a == ")"):
				return True
			else:
				return False
		elif (isinstance(a, ParseResults)):
			return self.isSingularCall(a)
		else:
			return False
	
	def parseExpressionToBytecode(self, results, mTCount, returnValueRequired, classObjectRegisterPos = None):
		global OPERATOR, FIRSTOPERAND, NEXTOPERAND
		mMode = FIRSTOPERAND
		created_register = False
		buf = ""
		operator = ""
		lastOperator = ""
		first_string_operand = None;
		first_string_operand_carry = None
		for i in results:
			if (mMode == FIRSTOPERAND):
				first_string_operand = None;
				if (isinstance(i, str)):
					if (i == "-" or i == "+"):
						# the first operand is a + or - sign
						operator = i
						mMode = NEXTOPERAND
						continue
					
					if (i.startswith('"') and i.endswith('"')):
						if (not created_register):
							buf += "NEWS mT" + str(mTCount) + ", \"\"\n"
							created_register = True
						buf += "ADD mT" + str(mTCount) + "," + str(self.escaper.evaluateEscapedCharacters(i)) + "\n"
						
					else:
						first_string_operand = i;
						
						if (len(results) == 1 and classObjectRegisterPos == None):
							buf += "CLR mT" + str(mTCount) + "\n"
							if (classObjectRegisterPos != None):
								buf += "RMOVP mT" + str(mTCount) + "," + classObjectRegisterPos + "," + first_string_operand + "\n"
							else:
								buf += "RMOVV mT" + str(mTCount) + "," + first_string_operand + "\n"
							created_register = True
					
				elif (isinstance(i, ParseResults)):
					# the first operand is another grouping
					#if (not created_register):
					#	buf += "NEWN mT" + str(mTCount) + ", 0\n"
					#	created_register = True
					
					ref, buf2 = self.parseExpressionToBytecode(i, mTCount + 1, returnValueRequired)
					buf += buf2
					buf += "BRS gCheck, mT" + str(ref) + "\n"
					buf += "NEWN mT" + str(mTCount) + ", 0\n"
					buf += "BRK gCheck\n"
					buf += "BRNS gCheck, mT" + str(ref) + "\n"
					buf += "DMOVV mT" + str(ref) + ", mT" + str(mTCount) + "\n"
					buf += "CLR mT" + str(ref) + "\n"
					buf += "BRK gCheck\n"
				else:
					# the first operand is a number
					if (not created_register):
						buf += "NEWN mT" + str(mTCount) + ", 0\n"
						created_register = True
					buf += "ADD mT" + str(mTCount) + "," + str(i) + "\n"
				mMode = OPERATOR
			elif (mMode == OPERATOR):
				mMode = NEXTOPERAND
				operator = i
			elif (mMode == SKIPOPERAND):
				mMode = OPERATOR
			elif (mMode == NEXTOPERAND):
				# main variable is now in mT(mTCount) [bytecode]
				# operator variable is now in operator [python]
				# second operand is in i
				
				func = first_string_operand
				
				# handle array operator
				if (operator == "["):
					if (isinstance(i, ParseResults)):
						if (classObjectRegisterPos != None):
							buf += self.parseArrayExpression(i, mTCount, first_string_operand, classObjectRegisterPos)
						else:
							buf += self.parseArrayExpression(i, mTCount, first_string_operand)
						mMode = SKIPOPERAND;
						continue;
					else:
						self.fatalError(i, "Invalid array index syntax.")
				# handle function operator
				if (operator == "("):
					if (isinstance(i, ParseResults) or first_string_operand == None):
						if (classObjectRegisterPos != None):
							buf += self.parseFunctionExpression(i, mTCount, first_string_operand, returnValueRequired, classObjectRegisterPos)
						else:
							buf += self.parseFunctionExpression(i, mTCount, first_string_operand, returnValueRequired)
						mMode = SKIPOPERAND;
						continue;
					else:
						self.fatalError(i, "Invalid function call syntax.")
				
				# handle object property operator
				if (operator == "."):
					if (lastOperator == ""):
						# we need to load the first part of the object
						# onto the stack (but only once!)
						buf += "CLR mT" + str(mTCount) + "\n"
						buf += "RMOVV mT" + str(mTCount) + "," + first_string_operand + "\n"
				
					#if (classObjectRegisterPos != None):
					#	buf += self.parseObjectPropertyExpression(i, mTCount, classObjectRegisterPos)
					#else:
					buf += self.parseObjectPropertyExpression(i, mTCount)
					
					lastOperator = "."
					mMode = OPERATOR;
					continue;
				
				# reset last operator
				lastOperator = ""
				
				# handle register loading
				if (classObjectRegisterPos == None):
					if (first_string_operand != None):
						buf += "CLR mT" + str(mTCount) + "\n"
						buf += "RMOVV mT" + str(mTCount) + "," + first_string_operand + "\n"
						created_register = True
					#elif (not created_register):
					#	buf += "NEWN mT" + str(mTCount) + ", 0\n"
					#	created_register = True
				
				# handle arithmetic operators
				if (operator == "+"):
					bop = "ADD";
				elif (operator == "-"):
					bop = "SUB";
				elif (operator == "*"):
					bop = "MUL";
				elif (operator == "/"):
					bop = "DIV";
				elif (operator == "^"):
					bop = "POW";
				elif (operator == "=="):
					bop = "EQ";
				elif (operator == "<="):
					bop = "LE";
				elif (operator == ">="):
					bop = "GE";
				elif (operator == "<"):
					bop = "LT";
				elif (operator == ">"):
					bop = "GT";
				elif (operator == "!="):
					bop = "NE";
				elif (operator == "!="):
					bop = "NE";
				else:
					self.fatalError(expression, "Unknown arithmetic operation requested.")
				
				if (isinstance(i, str)):
					if (i == "-" or i == "+"):
						# the second operand is a + or - sign
						operator = i
						mMode = NEXTOPERAND
						continue
					
					if (i.startswith('"') and i.endswith('"')):
						# the second operand is a string
						buf += "NEWS mTemp, " + str(self.escaper.evaluateEscapedCharacters(i)) + "\n"
						buf += bop + " mT" + str(mTCount) + ", mTemp\n"
						buf += "CLR mTemp\n"
					else:
				
						# the second operand is a variable
						if (classObjectRegisterPos == None):
							buf += "CLR mT" + str(mTCount + 1) + "\n"
							buf += "RMOVV mT" + str(mTCount + 1) + "," + i + "\n"
						buf += bop + " mT" + str(mTCount) + ",mT" + str(mTCount + 1) + "\n"
						buf += "CLR mT" + str(mTCount + 1) + "\n"
				elif (isinstance(i, ParseResults)):
					# the second operand is another grouping
					ref, buf2 = self.parseExpressionToBytecode(i, mTCount + 1, returnValueRequired)
					buf += buf2
					buf += bop + " mT" + str(mTCount) + ",mT" + str(ref) + "\n"
					buf += "CLR mT" + str(ref) + "\n"
				else:
					# the second operand is a number
					buf += "NEWN mTemp, " + str(i) + "\n"
					buf += bop + " mT" + str(mTCount) + ", mTemp\n"
					buf += "CLR mTemp\n"
				
				mMode = OPERATOR
				first_string_operand = None;
				
		return mTCount, buf
	
	def parseArrayExpression(self, result, mTCount, arrayName, classObjectRegisterPos = None):
		# Parses a result set that is a set of array
		# indexes.
		buf = ""
		
		# Load the array itself onto the stack at
		# mTCount.
		if (classObjectRegisterPos != None):
			buf += """DRMOVP """ + classObjectRegisterPos + """,""" + str(arrayName) + """,mT""" + str(mTCount) + """
"""
		else:
			buf += """BRS aTest, mT""" + str(mTCount) + """
RMOVV mT""" + str(mTCount) + """,""" + str(arrayName) + """
BRK aTest
"""
		
		for i in result:
			if (isinstance(i, float)):
				# WARNING: This will automatically convert
				#          the float to an int, which can
				#          be dangerous if the float is not
				#          storing an integer.
				i = int(i)
			
				# We are dealing with a static index.
				# Just shift it down the stack.
				buf += """DRMOVA mT""" + str(mTCount) + """, mATemp, """ + str(i) + """
CLR mT""" + str(mTCount) + """
DMOVV mATemp, mT""" + str(mTCount) + """
CLR mATemp
"""
			elif (isinstance(i, str)):
				# We are dealing with a dynamic index.
				# It is either a normal variable or
				# a static string (invalid).  We need to
				# copy the value of the variable into
				# a temporary register for DDRMOVA to
				# work.
				if (i.startswith('"') and i.endswith('"')):
					self.fatalError(i, "Invalid array index '" + str(i) + "'.")
				else:
					buf += """RMOVV mVTemp, """ + str(i) + """
DDRMOVA mT""" + str(mTCount) + """, mATemp, mVTemp
CLR mT""" + str(mTCount) + """
CLR mVTemp
DMOVV mATemp, mT""" + str(mTCount) + """
CLR mATemp
"""
			
			elif (isinstance(i, ParseResults)):
				# We are dealing with a dynamic index.
				# It could be a function or an array.
				# Use parseExpressionToBytecode to get
				# the value into a temporary register,
				# then use that as our index.
				ignore, bytecode = self.parseExpressionToBytecode(i, mTCount + 1, True, classObjectRegisterPos)
				
				buf += bytecode + """DDRMOVA mT""" + str(mTCount) + """, mATemp, mT""" + str(mTCount + 1) + """
CLR mT""" + str(mTCount) + """
CLR mT""" + str(mTCount + 1) + """
DMOVV mATemp, mT""" + str(mTCount) + """
CLR mATemp
"""
				
			else:
				# Unknown type of index, probably invalid.
				self.fatalError(i, "Invalid array index '" + str(i) + "'.")
		
		return buf;
	
	def parseFunctionExpression(self, result, mTCount, functionName, returnValueRequired, classObjectRegisterPos = None):
		# Parses a result set that is a set of function arguments.
		# The function will be at the mTCount register.
		
		astr00 = "a" + str(mTCount) + "00"
		astr01 = "a" + str(mTCount) + "01"
		buf = """NEWA """ + astr00 + """
"""
		a = 0
		for i in result:
			if (isinstance(i, float)):
				# Put the number into the argument array.
				buf += """NEWN """ + astr01 + """, """ + str(i) + """
DMOVA """ + astr01 + """, """ + astr00 + """, """ + str(a) + """
CLR """ + astr01 + """
"""
			elif (isinstance(i, str)):
				# We are dealing with either a string or
				# a standard variable.  See if the text
				# has " around it, and if so, strip and
				# treat as static string.
				if (i.startswith('"') and i.endswith('"')):
					buf += """NEWS """ + astr01 + """, \"""" + self.escaper.evaluateEscapedCharacters(i[1:-1]) + """\"
DMOVA """ + astr01 + """, """ + astr00 + """, """ + str(a) + """
CLR """ + astr01 + """
"""
				else:
					buf += """BVS vCheck, """ + str(i) + """
ERR -1, Undefined variable '""" + str(i) + """' while calling function '""" + functionName + """'.
BRK vCheck
RMOVV """ + astr01 + """,""" + str(i) + """
DMOVA """ + astr01 + """, """ + astr00 + """, """ + str(a) + """
CLR """ + astr01 + """
"""
			
			elif (isinstance(i, ParseResults)):
				# We are dealing with either a function
				# or an array - we'll use parseExpressionToBytecode
				# to get the output from it and place it
				# at mTCount + 1 which we will then load
				# onto the argument list.
				ignore, bytecode = self.parseExpressionToBytecode(i, mTCount + 1, True)
				
				buf += bytecode + """DMOVA mT""" + str(mTCount + 1) + """, """ + astr00 + """, """ + str(a) + """
CLR mT""" + str(mTCount + 1) + """
"""
				
			else:
				# Unknown type of index, probably invalid.
				self.fatalError(i, "Invalid value for argument #" + str(a) + " '" + str(i) + "'.")
			
			a += 1
		
		# Now we need to actually generate
		# the bytecode that calls the function
		# with the argument list and puts
		# the result on mTCount
		if (classObjectRegisterPos != None):
			buf += """CALLC """ + functionName + """,""" + astr00 + """,funcRet,""" + classObjectRegisterPos + """
"""
		else:
			buf += """CALLF """ + functionName + """,""" + astr00 + """,funcRet
"""
		
		if (returnValueRequired):
			return buf + """BRS fRCheck, funcRet
ERR -2, The function '""" + functionName + """' did not return a value when it is required in this expression.
BRK fRCheck
DMOVV funcRet, mT""" + str(mTCount) + """
CLR """ + astr00 + """
CLR funcRet
"""
		else:
			return buf + """BRS fRCheck, funcRet
NEWN funcRet, 0
BRK fRCheck
BRNS fRCheck, funcRet
DMOVV funcRet, mT""" + str(mTCount) + """
BRK fRCheck
CLR """ + astr00 + """
CLR funcRet
"""
	
	def parseObjectPropertyExpression(self, i, mTCount):
		# Parses a expression for an object property and attempts to
		# extract a variable from the object.
		
		buf = ""
		if (isinstance(i, str)):
			# the expression is a variable
			buf += "DRMOVP mT" + str(mTCount) + "," + i + ", mPTemp\n"
			buf += "CLR mT" + str(mTCount) + "\n"
			buf += "DMOVV mPTemp, mT" + str(mTCount) + "\n"
			buf += "CLR mPTemp\n"
		elif (isinstance(i, ParseResults)):
			# the expression is another grouping
			ref, buf2 = self.parseExpressionToBytecode(i, mTCount + 1, False, "mT" + str(mTCount))
			buf += buf2
			buf += "CLR mT" + str(mTCount) + "\n"
			buf += "DMOVV mT" + str(mTCount + 1) + ", mT" + str(mTCount) + "\n"
			buf += "CLR mT" + str(mTCount + 1) + "\n"
			
			#buf += bop + " mT" + str(mTCount) + ",mT" + str(ref) + "\n"
			#buf += "CLR mT" + str(ref) + "\n"
			#self.fatalError(i, "Complex object properties and function calling is not supported.");
		else:
			# the second operand is a number
			self.fatalError(i, "Invalid property name.");
		
		return buf;
	
	def fatalError(self, expression_text, error_text):
		if (self.parent == None):
			# Running as a standalone parser.
			print self.parent.engine.before_error + """Traceback (most recent call last):
  File """ + '"<string>"' + """, line ?
    """ + str(expression_text) + """

SyntaxError: """ + str(error_text)
			sys.exit()
		else:
			# Running as part of the bytecode / interpreter.
			print self.parent.engine.before_error + """Traceback (most recent call last):
  File """ + '"' + self.parent.current_file + '"' + """, line """ + str(self.parent.current_line) + """
    """ + str(expression_text) + """

SyntaxError: """ + str(error_text)
			sys.exit()